<?php

/**
 * This file is part of the Codizer package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright   Copyright (c) 2010 Advisa (http://www.advisa.fr)
 * @author      Codizer Core Team <team@codizer.org>
 * @package     Codizer
 */

class f_core_Controller
{	
	/**
	 * @var f_core_Controller
	 */
	private static $instance;
	
	private $module;
	private $action;
	private $className;
	
	private $returnException = true;
	
	private $chain;
	
	const DEFAULT_ACTION_METHOD = "execute";
	const DEFAULT_ERRORHANDLER_METHOD = "errorHandler";
	const DEFAULT_VALIDATE_METHOD = "validate";

	private function __construct() {}

	/**
	 * @return f_core_Controller
	 */
	public static function getInstance()
	{	
		if (self::$instance === null)
		{
			self::$instance = new self();
		}
		return self::$instance;
	}

    public function isBackOffice()
    {
        f_core_Context::getInstance()->beginBackSession();
        return $this;
    }

	/**
	 * Disable exception messages
	 *
	 * @return f_core_Controller
	 */
	public function disableException()
	{
		$this->returnException = false;
		return $this;
	}
	
	/**
	 * enable exception messages
	 *
	 * @return f_core_Controller
	 */
	public function enableException()
	{
		$this->returnException = true;
		return $this;
	}
	
	/**
	 * Get the current exception return status
	 *
	 * @return boolean
	 */
	public function getExceptionReturnStatus()
	{
		return $this->returnException;
	}
	
	/**
	 * Main dispatcher
	 *
	 * @return string
	 */
	public function dispatch()
	{
		$startTime = microtime(true);
		$request = f_core_Request::getInstance();
		$logger = f_logs_Logger::getInstance();

		$lastAction = null;
		$mv = null;
		
		if(!isset($this->action) && !isset($this->module))
		{
			// Get the action to launch from HTTP request
			$lastAction = 1;
			$this->getActionFromRequest($request);	
		}
		
		$this->chain['modules'][] = $this->module;
		$this->chain['actions'][] = $this->action;					

		$this->className = "w_".$this->module."_actions_".$this->action."Action";
		
		if(!class_exists($this->className))
		{
			$this->className = "m_".$this->module."_actions_".$this->action."Action";
		}
		else
		{
			if(!method_exists($this->className, self::DEFAULT_ACTION_METHOD))
			{
				if($this->returnException == true)
				{
					throw new Exception(self::DEFAULT_ACTION_METHOD."() not found in ".$this->className);
				}
				else
				{
					$logger->exceptionLog(self::DEFAULT_ACTION_METHOD."() not found in ".$this->className);
					exit();
				}
			}
		}
		
		$this->chain['classNames'][] = $this->className;
		
		$logger->actionLog("-> START ".$this->module."/".$this->action." (".$this->className.")");
		
		// If there is a validate() method in the action class...
		if($this->checkAction())
		{
			if($this->isValidationMethod())
			{
				//...and if the method return true
				if($this->isValidationOk())
				{
					//... process execute()
					$mv = $this->processAction();
				}
				else
				{
					//... else if is a errorHandler
					if($this->isErrorHandler())
					{					
						//... process handleError()
						$mv = $this->handleError();	
					}
					else
					{
						// @todo : add generic error handler
						return null;				
					}
				}			
			}
			else //... if no validation method, call directly execute()
			{
				$mv = $this->processAction();
			}	
		}

		if(is_string($mv))
		{
			echo $mv;
		}
		
		$view = "";
		
		if(is_object($mv))
		{
			$view = $this->processView($mv);
		}
		
		$time = round(microtime(true)-$startTime, 5);

		$modules = $this->chain['modules'];
		$actions = $this->chain['actions'];
		$classNames = $this->chain['classNames'];
		
		$module = end($modules);
		$action = end($actions);
		$className = end($classNames);

		$logger->actionLog("-> END ".$module."/".$action." (".$className.")", $time);
		
		array_pop($this->chain['modules']);
		array_pop($this->chain['actions']);
		array_pop($this->chain['classNames']);
				
		$this->enableException();
		
		return $view;
	}
	
	/**
	 * Call execute() method in the action class
	 *
	 * @param f_core_Request $request
	 * @return mixed
	 */
	public function processAction()
	{					
		$methodName = self::DEFAULT_ACTION_METHOD;
		$class = new $this->className();
		$execute = $class->$methodName();
        return is_string($execute) ? $execute : $class;
	}	
	
	/**
	 * Retrieve the view content
	 *
	 * @param f_core_Action $model
	 * @return string
	 */
	private function processView(f_core_Action $model)
	{
		$logger = f_logs_Logger::getInstance();
		
		if(!is_null($model->getView()))
		{	
			$class = $this->getViewNameByClassName(get_class($model));
					
			$classSuffix = $class['moduleName']."_views_".$class['viewName'].ucfirst($model->getView())."View";
		
			$viewClass = class_exists('w_'.$classSuffix) ? 'w_'.$classSuffix : 'm_'.$classSuffix;
			
			if(class_exists($viewClass))
			{			
				$class = new $viewClass();
				$class->setModel($model->getModel());
				return $class->execute();
			}
			else
			{
				if($this->returnException == true)
				{
					throw new Exception($viewClass." does not exist !");
				}
				else
				{
					$logger->exceptionLog($viewClass." does not exist !");
					exit();
				}
			}
		}			
	}
	
	/**
	 * Return the view name by the class name
	 *
	 * @param string $className
	 * @return array
	 */
	private function getViewNameByClassName($className)
	{
		$className = explode("_", $className);
		$moduleName = $className[1];
		unset($className[0], $className[1], $className[2]);
		
		$actionName = join("_", $className);
		
		$result['viewName'] = str_replace("Action", "", $actionName);
		$result['moduleName'] = $moduleName;
		return $result;
	}
	
	/**
	 * Set the module to launch
	 *
	 * @param string $moduleName
	 * @return f_core_Controller
	 */
	public function setModule($moduleName)
	{
		$this->module = $moduleName;
		return $this;
	}
	
	/**
	 * Set the action to launch
	 *
	 * @param string $actionName
	 * @return f_core_Controller
	 */
	public function setAction($actionName)
	{
		$args = explode("_", $actionName);
		
		if(count($args) > 1)
		{
			$args[count($args)-1] = ucfirst($args[count($args)-1]);
			$actionName = join("_", $args);
		}
		$this->action = $actionName;
		return $this;
	}	
	
	/**
	 * Get the parameters from HTTP request
	 *
	 * @param f_core_Request $request
	 */
	private function getActionFromRequest(f_core_Request $request)
	{
		$logger = f_logs_Logger::getInstance();
		
		if (!$request->hasParameter('module'))
		{
			if($request->hasParameter("url"))
			{
				$route = f_core_Routing::getInstance($request->getParameter("url"))->setRequestURL($request->getParameter("url"));
				$this->module = $route->getModule(); 
				$this->action = $route->getAction(); 
			}
			else
			{
				$config = f_loader_Config::getInstance()->getParameters("default");
				$this->module = $config['default_module'];
				$this->action = $config['default_action'];
			}
			
			$logger->requestLog("module=".$this->module."&action=".$this->action);
		}
		else
		{
			$this->module = $request->getParameter('module');
			
			$actionName = $request->getParameter('action', 'index');
						
			$args = explode("_", $actionName);
			
			if(count($args) > 1)
			{
				$args[count($args)-1] = ucfirst($args[count($args)-1]);
				$actionName = join("_", $args);
			}
			
			$this->action = $actionName;
			
			$params = $request->getParameters();
			
			$res = array();
			foreach($params as $key => $param)
			{
				if($key != "module" && $key != "action")
				{
					if(isset($param[0]))
					{
						$res[] = "&".$key."=".$param[0];
					}
				}
			}
			
			$logger->requestLog("module=".$this->module."&action=".$this->action.join("", $res));
		}
	}	
	
	/**
	 * Check if the action exist
	 * @return exception
	 */
	private function checkAction()
	{
        if($this->module == '' || $this->action == '')
        {
            $suffix = f_loader_Config::getInstance()->getConfigSuffix();
            echo '<h1>Codizer configuration</h1>';
            echo "Please configure a default module and a default action in your configuration file (webapp/config.".$suffix.'.ini)';
            exit;
        }

		$logger = f_logs_Logger::getInstance();
					
		if(!class_exists($this->className))
		{
			if($this->returnException == true)
			{
				throw new Exception("Class ".$this->className." not found");
			}
			else
			{
				$logger->exceptionLog("Class ".$this->className." not found");
				return false;
			}
		}
		
		if(!method_exists($this->className, self::DEFAULT_ACTION_METHOD))
		{
			if($this->returnException == true)
			{
				throw new Exception(self::DEFAULT_ACTION_METHOD."() not found in ".$this->className);
			}
			else
			{
				$logger->exceptionLog(self::DEFAULT_ACTION_METHOD."() not found in ".$this->className);
				return false;
			}
		}
		
		return true;
	}

    public static function isFileFromClass($className)
    {
        $args = explode('_', $className);

        $firstChar = $args[0];

        switch ($firstChar)
        {
            // if first character is "f", class is in the FRAMEWORK_HOME folder
            case "f":
                $directory = FRAMEWORK_HOME;
            break;

            case "m":
                $directory = MODULES_DIR;
            break;

            case "w":
                $directory = WEBAPP_MODULES_DIR;
            break;
        }

        unset($args[0]);

        // set the class file path
        $file = $directory.DS.implode(DS, $args).".class.php";

        return is_file($file);
    }
	
	/**
	 * Check if a errorHandler() method exist in the action class
	 *
	 * @return boolean
	 */
	private function isErrorHandler()
	{
		return $this->methodExists(self::DEFAULT_ERRORHANDLER_METHOD);	
	}
	
	/**
	 * Check if a validation() method exist in the action class
	 *
	 * @return boolean
	 */
	private function isValidationMethod()
	{
		return $this->methodExists(self::DEFAULT_VALIDATE_METHOD);
	}
	
	/**
	 * Check if a method exists in the action class
	 *
	 * @param string $methodName
	 * @return boolean
	 */
	private function methodExists($methodName)
	{
		return method_exists($this->className, $methodName);
	}	

	/**
	 * Call validation() method in the action class and retrieve the result
	 *
	 * @return boolean
	 */	
	private function isValidationOk()
	{
		$methodName = self::DEFAULT_VALIDATE_METHOD;
		$class = new $this->className();
		return $class->$methodName();
	}		
	
	/**
	 * Call handleError() method in the action class
	 *
	 * @param f_core_Request $request
	 * @return string|exception
	 */	
	private function handleError()
	{
		$methodName = self::DEFAULT_ERRORHANDLER_METHOD;
		$class = new $this->className();		
        $execute = $class->$methodName();
        return is_string($execute) ? $execute : $class;
	}
	
	/**
	 * Send a raw HTTP header
	 *
	 * @param $string
	 * @param $replace
	 * @param $http_response_code
	 * @return string|exception
	 */		
	public function redirect($string, $replace = null, $http_response_code = null)
	{
        if(f_core_Session::getInstance()->getValue("indexation", false) != true)
        {
            $string = str_ireplace("location:", "", $string);
            header("Location:".$string, $replace, $http_response_code);
            exit;
        }
	}
}